package fons.navigator.ui.browsers.dimensions;

import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Set;

import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.nodes.PPath;
import fons.navigator.util.box2d.Box2D;
import fons.navigator.util.piccolo.ContextMenu;
import fons.navigator.util.piccolo.ContextMenuOwner;

public abstract class Block extends PPath implements ContextMenuOwner {
	private static final long serialVersionUID = 1L;

	private final DimensionBrowserCanvas canvas;
	private Body body;
	
	private final Set<Connection> connections = Sets.newHashSet();
	private final ContextMenu menu;
	
	public Block(DimensionBrowserCanvas canvas) {
		this.canvas = canvas;
		menu = createMenu();
		this.addInputEventListener(new BlockEventHandler());
	}

	private ContextMenu createMenu() {
		final ContextMenu menu = new ContextMenu(canvas.getCamera());
		menu.add("Position fixieren", new Runnable() {
			@Override
			public void run() {
				setFixated(!isFixated());
			}
		});
		return menu;
	}
	
	@Override
	public ContextMenu getMenu() {
		return menu;
	}

	public DimensionBrowserCanvas getCanvas() {
		return canvas;
	}
	
	public Body getBody() {
		return this.body;
	}

	public void updatePosition() {
		if (!body.isAwake()) return ;
		final Vec2 p = body.getPosition();
		final Point2D translation = getGlobalTranslation();

		translate((Box2D.toPiccolo(p.x) - 0.5f * (float)getWidth()) - translation.getX() ,
				  (Box2D.toPiccolo(p.y) - 0.5f * (float)getHeight()) - translation.getY());
	}
	
	public void setFixated (boolean fixated) {
		createBody (fixated ? BodyType.STATIC : BodyType.DYNAMIC);
	}
	
	public boolean isFixated () {
		return getBodyType().equals(BodyType.STATIC);
	}
	
	public void createBody (BodyType type) {
		newBody(canvas.getWorld(), type);
		for (Connection c: connections) {
			c.rejoin();
		}
	}

	private void newBody(World world, BodyType type) {
		if (body != null) {
			canvas.getWorld().destroyBody(body);
		}
		
		BodyDef bd = new BodyDef();
		bd.type = type;
				
		final Point2D position = getGlobalTranslation();
		bd.position.set(
				Box2D.toBox2D(position.getX() + getWidth()/2.0f),
				Box2D.toBox2D(position.getY() + getHeight()/2.0f));

		bd.linearDamping = 3.0f;
		final Body body = canvas.getWorld().createBody(bd);
		body.setUserData(this);

		final CircleShape circle = new CircleShape();
		final float radius = Box2D.toBox2D(getHeight()/3.0);
		circle.m_radius = radius;
		circle.m_p.set(new Vec2(0,0));

		final FixtureDef fd = new FixtureDef();
		fd.friction = 0.8f;
		fd.density = .1f / (float)(Math.PI * radius * radius);
		fd.restitution = 0.1f;
		fd.shape = circle;
		fd.filter.categoryBits = 2;
		fd.filter.maskBits = 2;
		body.createFixture(fd);

		this.body = body;
	}
	
	public BodyType getBodyType() {
		return body.getType();
	}

	public void addConnection(Connection c) {
		connections.add(c);
	}
	
	public boolean isConnectedTo (Block b) {
		for (Connection c: connections) {
			if (c.connects(b)) return true;
		}
		return false;
	}

	public Connection getConnectionTo(Block b) {
		for (Connection c: connections) {
			if (c.connects(b)) return c;
		}
		return null;
	}

	public void removeConnection(Connection c) {
		connections.remove(c);
	}
	
	public Collection<Connection> getConnections() {
		return Lists.newArrayList(connections);
	}
	
	public Iterable<Connection> getConnections (Predicate<Connection> predicate) {
		return Iterables.filter(connections, predicate);
	}

	protected class BlockEventHandler extends PBasicInputEventHandler {
		private final Set<Connection> rejoinees = Sets.newHashSet();
		
		@Override
		public void mouseDragged(PInputEvent event) {	
			if (event.isLeftMouseButton()) {
				if (!isFixated()) {
					getBody().applyForce(new Vec2(Box2D.toBox2D(event.getDelta().getWidth()*15),
							Box2D.toBox2D(event.getDelta().getHeight()*15)),
							body.getWorldCenter());

				} else {
					final Vec2 boxPos = new Vec2(
							Box2D.toBox2D(event.getPosition().getX()),
							Box2D.toBox2D(event.getPosition().getY())
							);
					getBody().setTransform(boxPos, 0);
					
					for (Connection c: connections) {
						Body otherBody = c.getOpposite(Block.this).getBody();
						otherBody.setActive(true);
						otherBody.setAwake(true);
					}
				}
			}
		}
		
		@Override
		public void mousePressed(PInputEvent event) {
			if (event.isControlDown()) {
				rejoinees.addAll(Block.this.getConnections());
				Connection.disjoinAll(rejoinees);
				
			} else if (!isFixated()) {
				for (Connection connectionToStatic: Block.this.getConnections(new Predicate<Connection>() {
					@Override
					public boolean apply(Connection input) {
						return input.getOpposite(Block.this).isFixated();
					}
				})) {
					connectionToStatic.disjoin();
					rejoinees.add(connectionToStatic);
				}				
			}
			super.mousePressed(event);
		}
		
		@Override
		public void mouseReleased(PInputEvent event) {
			if (rejoinees.size() > 0) {
				Connection.rejoinAll(rejoinees);
				rejoinees.clear();
			}
			super.mouseReleased(event);
		}
	}

	public void setHighlighted(boolean b) {
		
	}

	public void modifyConnectionMenu(ConnectionMenu menu) {
		
	}
	
	public static <T extends Block> Iterable<T> filter (Iterable<Block> coll, Class<T> clazz) {
		return Iterables.filter(coll, clazz);
	}
}